"""The standard display widgets"""

from PyQt4 import QtGui, QtCore
from itertools import count
from types import GeneratorType
from fileAcceptors import getAcceptor, MockAcceptor

class DragMixin(object):
    """A mixin to handle dragging of files into a Qt Widget"""
    def __init__(self, name):
        self.setAcceptDrops(True)
        self.fileAcceptor=MockAcceptor(self)
    def __checkData(self, data):
        """Checks to see if a file is of a valid extension for this"""
        return self.fileAcceptor.checkFile(str(data.path()))
    def dragEnterEvent(self, event):
        if any(self.__checkData(data) for data in event.mimeData().urls()):
            event.acceptProposedAction()
    def dragMoveEvent(self, event):
        event.acceptProposedAction()
    def dropEvent(self, event):
        self.fileAcceptor.accept(str(data.path()) for data in
                                 event.mimeData().urls()
                                 if self.__checkData(data))
        event.acceptProposedAction()
    def setAcceptor(self, name):
        """For setting the object responsible for accepting a file"""
        self.fileAcceptor=getAcceptor(name)(self)
        
class MockListener(object):
    def loadElements(self, elements, selectedp): pass

class TableWidget(DragMixin, QtGui.QTableWidget):
    """Displays a table of elements according to the attributes of a tray"""
    sSelectedElements=QtCore.pyqtSignal(GeneratorType)
    def __init__(self, elements, atts, parent=None):
        QtGui.QTableWidget.__init__(self, parent)
        DragMixin.__init__(self, ['mp3'])
        self.cellChanged.connect(self.__onCellChanged)
        self.itemSelectionChanged.connect(self.__onSelectionChanged)
        self.setAlternatingRowColors(True)
        self.listener=MockListener()
        self.setColumns(atts)
        self.setElements(elements)
        self.show()
    def setColumns(self, atts):
        """Sets the columns according to the attributes passed"""
        self.__atts=atts
        for i in xrange(len(atts)):
            self.insertColumn(i)
            self.setColumnWidth(i, 150)
        self.setHorizontalHeaderLabels(atts)
    def setElements(self, elements, sortingp=True):
        """Sets a group of elements to be those displayed by the table"""
        self.clearContents()
        self.setRowCount(0)
        self.elements=elements
        self.__itemToElement={}
        self.addElements(elements, sortingp)
    @classmethod
    def __getAtt(cls, element, key):
        """Returns the text for a specific att"""
        #Replace with strategy when grows
        if '-' in key:
            keys=key.split('-')
            if all(type(cls.__getAtt(element, key))!=list for key in keys):
                return '-'.join(cls.__getAtt(element, key) for key in keys)
            else:
                values=[]
                values=[value if type(value)==list else [value]
                        for value in values]
                try:
                    maxLen=max(len(value) for value in values)
                except ValueError:
                    return ''
                for value in values:
                    while len(value)<maxLen:
                        value.append(value[-1])
                return ['-'.join(parts) for parts in zip(values)]
        else:
            att=element[key] if key in element else ''
            if type(att)==int:
                att=str(att)
            return att
    def addElements(self, elements, sortingp=True):
        """Adds elements into the table"""
        self.cellChanged.disconnect(self.__onCellChanged)
        if sortingp: self.setSortingEnabled(False)
        row=self.rowCount()
        for element in elements:
            self.insertRow(row)
            rowWidget=None
            for key, column in zip(self.__atts, count()):
                att=self.__getAtt(element, key)
                try:
                    widget=QtGui.QTableWidgetItem(att)
                    self.setItem(row, column, widget)
                except TypeError:
                    widget=_ComboTableWidgetItem(att)
                    self.setCellWidget(row, column, widget)
                if rowWidget is None: #Sets rowWidget=leftmost widget of row
                    rowWidget=widget
            self.__itemToElement[rowWidget]=element
            row+=1
        if sortingp: self.setSortingEnabled(True)
        self.cellChanged.connect(self.__onCellChanged)
    def __getRowWidget(self, row):
        """Gets the widget corresponding to the first cell of the row,
        whether item or cell widget"""
        item=self.item(row, 0)
        if item:
            return item
        else:
            return self.cellWidget(row, 0)
    def filterElements(self, filterFunction):
        """Hides all elements which do not satisfy the filter function
        passed"""
        for row in xrange(self.rowCount()):
            element=self.__itemToElement[self.__getRowWidget(row)]
            if filterFunction(element):
                self.showRow(row)
            else:
                self.hideRow(row)
    def getElements(self, row=0):
        """Returns a generator of that row's element and all elements below
        it"""
        return (self.__itemToElement[self.__getRowWidget(i)]
                for i in xrange(row, self.rowCount())
                if not self.isRowHidden(i))
    def __selectedElements(self):
        """Gets the set of currently selected elements and rows"""
        rows=[index.row() for index in self.selectedIndexes()]
        elements=(self.__itemToElement[self.__getRowWidget(row)]
                  for row in rows)
        return rows, elements
    def keyPressEvent(self, event):
        """Handles deletion or selection, otherwise passes on"""
        rows, elements=self.__selectedElements()
        #If this grows, replace with a strategy pattern
        if event.matches(QtGui.QKeySequence.Delete):
            for row in rows[::-1]:
                #reversed so that row does not have to be changed for removal
                del self.__itemToElement[self.__getRowWidget(row)]
                self.removeRow(row)
            for element in elements:
                self.elements.remove(element)
        elif event.key()==16777220: #Enter key of selection
            if len(rows)==1:
                self.listener.loadElements(self.getElements(rows[0]), False)
            else:
                self.listener.loadElements(elements, True)
        elif event.key()==32: #Space key of selection
            if len(rows)==1:
                self.listener.alternateAction(self.getElements(rows[0]),False)
            else:
                self.listener.alternateAction(elements, True)
        else:
            QtGui.QTableWidget.keyPressEvent(self, event)
    def mouseDoubleClickEvent(self, mouseEvent):
        self.listener.loadElements(self.getElements(self.currentRow()))
    @QtCore.pyqtSlot(int, int)
    def __onCellChanged(self, row, column):
        """Changes the element when the corresponding cell changes"""
        element=self.__itemToElement[self.__getRowWidget(row)]
        att=self.__atts[column]
        try:
            new=str(self.item(row, column).text())
            element[att]=new
        except AttributeError:
            widget=self.cellWidget(row, column)
            new=str(widget.currentText())
            element[att][widget.currentIndex]=new
    def changeElements(self, att, value):
        """Changes the selected elements to have element[att]=value"""
        column=self.__atts.index(att)
        for row in self.__selectedElements()[0]:
            try:
                self.item(row, column).setText(value)
            except AttributeError:
                self.cellWidget(row, column).addItem(value)
    @QtCore.pyqtSlot()
    def __onSelectionChanged(self):
        """Triggers the elements selected signal"""
        self.sSelectedElements.emit(self.__selectedElements()[1])
        
class _ComboTableWidgetItem(QtGui.QComboBox):
    """A custom combo box for placing inside the table"""
    def __init__(self, items):
        QtGui.QComboBox.__init__(self)
        self.addItems(items)
        self.setEditable(True)
    text=lambda self: self.currentText()
